/**
 * AGNEquatorialRegion.java
 */

package agngui.agnmodel;

import java.util.ArrayList;
import java.util.List;

import javax.media.j3d.Appearance;
import javax.media.j3d.Geometry;
import javax.media.j3d.GeometryArray;
import javax.media.j3d.Material;
import javax.media.j3d.QuadArray;
import javax.media.j3d.Transform3D;
import javax.vecmath.AxisAngle4d;
import javax.vecmath.Color3f;
import javax.vecmath.Point3d;
import javax.vecmath.Vector3d;
import javax.vecmath.Vector3f;


/**
 * Manages the properties and geometry of the Equatorial region.
 */
public class AGNEquatorialRegion extends AGNObject {
    /**
     * AGNEquatorialRegion
     *
     * A helper for the protected constructor-- creates the equatorial region geometry.
     *
     * @param ri Radius of the inner sphere in meters
     * @param ro Radius of the outer sphere in meters
     * @param ha half-angle of the equatorial region in degrees
     * @param n  The number of geometry facets to make up the equatorial region
     * @param modelGeometry A reference to the parent geometry object
     */
    public AGNEquatorialRegion( double ri, double ro, double ha, int n, AGNModelGeometry modelGeometry) {
    	this(ri, ro, ha, n, AGNModelGeometry.SCATTEREQUATORIAL, modelGeometry);
    }

    /**
     * AGNEquatorialRegion
     *
     * Creates the equatorial region geometry.
     *
     * @param ri Radius of the inner sphere in meters
     * @param ro Radius of the outer sphere in meters
     * @param ha half-angle of the equatorial region in degrees
     * @param id This object's identifier string
     * @param n  The number of geometry facets to make up the equatorial region
     * @param modelGeometry A reference to the parent geometry object
     */    
    protected AGNEquatorialRegion( double ri, double ro, double ha, int n, String id, AGNModelGeometry modelGeometry ) {
        super( id, modelGeometry );
        
        this.Ri = ri;
        this.Ro = ro;
        this.ha = ha;
        this.n  = n;

        geometry = createGeometry();
		for(Geometry g : geometry) {
			addGeometry( g );
		}

        setAppearance(createAppearance());

        transformGroup.addChild( this );
    }

    /**
     * createPoints
     *
     * Generates the points that make up this geometry in no particular order.
     *
     * @param ri The object's inner radius in meters
     * @param ro The object's outer radius in meters
     * @param ha The object's half angle in degrees
     * @param n  The number of geometric facets making up this object.
     */
    private Point3d[] createPoints( double ri, double ro, double ha, int n) {
        double dTheta = (2 * Math.PI) / n;
        Point3d vertices[]  = new Point3d[4*n];
        norm_array = new Vector3f[4*n];
        int offset = 0;
        /* Generate points 
		 * 
		 * The first n points are the top disk's inner radius points.
		 * The next n points are the top disk's outer radius points
		 * The next n are the bottom disk's inner radius points
		 * The final n are the bottom disk's outer radius points
		 */
		
        for( double theta = 0; theta < (2 * Math.PI); theta += dTheta ) {
			Point3d inner_top_disk = new Point3d(ri * Math.cos(theta), ri * Math.sin(theta), ri*Math.tan(ha));
			Point3d outer_top_disk = new Point3d(ro * Math.cos(theta), ro * Math.sin(theta), ro*Math.tan(ha));
			Point3d inner_bottom_disk = new Point3d(ri * Math.cos(theta), ri * Math.sin(theta), -ri*Math.tan(ha));
			Point3d outer_bottom_disk = new Point3d(ro * Math.cos(theta), ro * Math.sin(theta), -ro*Math.tan(ha));
			
			vertices[offset] = inner_top_disk;
			vertices[n + offset] = outer_top_disk;
			vertices[2*n + offset] = inner_bottom_disk;
			vertices[3*n + offset] = outer_bottom_disk;
			norm_array[offset] = getVertexNormal(inner_top_disk, inner_bottom_disk, outer_top_disk);
			norm_array[n + offset] = getVertexNormal(outer_top_disk, outer_bottom_disk, inner_top_disk);
			norm_array[2*n + offset] = getVertexNormal(inner_bottom_disk, inner_top_disk, outer_bottom_disk);
			norm_array[3*n + offset] = getVertexNormal(outer_bottom_disk, outer_top_disk, inner_bottom_disk);
			offset++;
        }

        return vertices; 
    }

    /**
     * createGeometry
     *
     * Create the object geometry based on the internal variable states
     */
    protected Geometry[] createGeometry() {
        double ha = this.ha * Math.PI / 180.0;    // Degrees to radians
               ha = (Math.PI / 2) - ha;           // Complement       
        Point3d[]          verts        = createPoints( Ri, Ro, ha, n );
		List<Point3d> quad_points = new ArrayList<Point3d>();
		List<Vector3f> normal_list = new ArrayList<Vector3f>();

		/* ************************** Create Geometry (quads) **************************** */
        // Top disk
        for( int i = 0; i < n; i++) {
			int bound = i+n+1;//boundary connection, needs to overflow 2*n back to n
			if(bound == 2*n) {
				bound = n;
			}
			int bound2 = i+1;//boundary, needs to overflow n back to 0
			if(bound2 == n) {
				bound2 = 0;
			}
			quad_points.add(verts[i]);
			quad_points.add(verts[i+n]);
			quad_points.add(verts[bound]);
			quad_points.add(verts[bound2]);
			normal_list.add(norm_array[i]);
			normal_list.add(norm_array[i+n]);
			normal_list.add(norm_array[bound]);
			normal_list.add(norm_array[bound2]);
			//System.out.println((i) + " " + (i+n) + " " + bound + " " + bound2);
        }

        // Bottom disk
        for( int i = 2*n; i < 3*n; i++) {
			int bound = i+n+1;//boundary, needs to overflow 4*n back to 3*n
			if(bound == 4*n) {
				bound = 3*n;
			}
			int bound2 = i+1;//boundary, needs to overflow 3*n back to 2*n
			if(bound2 == 3*n) {
				bound2 = 2*n;
			}
			quad_points.add(verts[i+n]);
			quad_points.add(verts[i]);
			quad_points.add(verts[bound2]);
			quad_points.add(verts[bound]);
			//System.out.println((i+n) + " " + (i) + " " + bound2 + " " + bound);
			normal_list.add(norm_array[i+n]);
			normal_list.add(norm_array[i]);
			normal_list.add(norm_array[bound2]);
			normal_list.add(norm_array[bound]);
        }

        // Inner ring
        for( int i = 0; i < n; i++ ) {
			int bound = i+1;//boundary, needs to overflow back to zero
			if(bound == n) {
				bound = 0;
			}
			int bound2 = i+2*n+1;//boundary, needs to overflow from 3*n back to 2*n
			if(bound2 == 3*n) {
				bound2 = 2*n;
			}
            quad_points.add(verts[i]);
			quad_points.add(verts[bound]);
			quad_points.add(verts[bound2]);
			quad_points.add(verts[i+2*n]);
			normal_list.add(norm_array[i]);
			normal_list.add(norm_array[bound]);
			normal_list.add(norm_array[bound2]);
			normal_list.add(norm_array[i+2*n]);
        }

        // Outer ring
        for( int i = n; i < 2*n; i++ ) {
			int bound = i+1;//boundary, needs to overflow from 2*n back to just n
			if(bound == 2*n) {
				bound = n;
			}
			int bound2 = i+2*n+1;//boundary, needs to overflow from 4*n back to 3*n
			if(bound2 == 4*n) {
				bound2 = 3*n;
			}
            quad_points.add(verts[i]);
			quad_points.add(verts[i+2*n]);
			quad_points.add(verts[bound2]);
			quad_points.add(verts[bound]);
			normal_list.add(norm_array[i]);
			normal_list.add(norm_array[i+2*n]);
			normal_list.add(norm_array[bound2]);
			normal_list.add(norm_array[bound]);
        }
		point_array = quad_points.toArray(new Point3d[0]);
		norm_array = normal_list.toArray(new Vector3f[0]);
		
		GeometryArray geometry_array = new QuadArray(point_array.length, GeometryArray.COORDINATES | GeometryArray.NORMALS);
		geometry_array.setCoordinates(0, point_array);
		geometry_array.setNormals(0, norm_array);
		
        Geometry[] geometryArray = new Geometry[]{ geometry_array };

		generateHandles();
		
        return geometryArray;
    }

    /**
     * createAppearance
     *
     * Create and get the default appearance for the equatorial region:
     * No transparency
     * Diffuse color (0, 1, 0)
     * Ambient color (1, .25, 0)
     *
     * @return A reference to the newly generated appearance.
     */
    protected Appearance createAppearance() {
        Appearance retVal = getAppearance();
		Material material = retVal.getMaterial();
		material.setDiffuseColor(new Color3f(0, 1.0f, 0));
		material.setAmbientColor(new Color3f(0, 0.25f, 0));
		retVal.setMaterial(material);
		
        return retVal;
    }

    /**
     * generateHandles
     *
     * Generates the HUD handles for the equatorial region based on the internal 
     * variable states.
     */
    public void generateHandles() {
        Point3d     points[]               = new Point3d[5];
        Vector3d    linHandleAxes          = new Vector3d( 1.0, 0.0, 0.0 );
        Vector3d    anHandleAxes           = new Vector3d( 0.0, 1.0, 0.0 );
        Vector3d    rotHandleAxisPolar     = new Vector3d( 1.0, 0.0, 0.0 );
        Vector3d    rotHandleAxisAzimuthal = new Vector3d( 0.0, 0.0, 1.0 );
        AxisAngle4d polarRotation          = new AxisAngle4d();
        Transform3D axisTransform          = new Transform3D();
        Transform3D objectTransform;
        double ha          = this.ha * Math.PI / 180;
        double anElevation = Ro * Math.sin( (Math.PI / 2) - ha );
        double anLength    = Ro * Math.cos( (Math.PI / 2) - ha );
        double rotDistance = Ro * 1.5;
        double rotRadius   = Ri * 2;
        double recentPolarValue     = (handles == null) ? 0.0 : handles[3].getRecentValue();
        double recentAzimuthalValue = (handles == null) ? 0.0 : handles[4].getRecentValue();

        if( handles == null ) {
            handles = new AGNParameterHandle[points.length];
        } else {
            // Transform polar axis according to azimuthal angle
            polarRotation.set( rotHandleAxisAzimuthal, recentAzimuthalValue );
            axisTransform.setRotation( polarRotation );
        }

        objectTransform = getFullTransform();

        points[0] = new Point3d( Ri, 0.0, 0.0 );
        points[1] = new Point3d( Ro, 0.0, 0.0 );

        // Angular handle
        points[2] = new Point3d( anLength, 0.0, anElevation );

        // Polar handle
        points[3] = new Point3d( rotDistance, 0.0, rotRadius );
        points[3].scale( SCALE_FACTOR );

        // Azimuthal handle
        points[4] = new Point3d( rotRadius, 0.0, rotDistance );
        points[4].scale( SCALE_FACTOR );

        objectTransform.transform( points[0] );
        objectTransform.transform( points[1] ); 
        objectTransform.transform( points[2] );
        axisTransform.transform( points[3] );
        objectTransform.transform( linHandleAxes  );
        objectTransform.transform( anHandleAxes   );
        axisTransform.transform( rotHandleAxisPolar );

        handles[0] = new AGNLinearParameterHandle( this, points[0], linHandleAxes, ID_RI ); // Inner radius
        handles[1] = new AGNLinearParameterHandle( this, points[1], linHandleAxes, ID_RO ); // Outer radius
        handles[2] = new AGNAngularParameterHandle( this, points[2], anHandleAxes, ID_HA ); // Half angle
        handles[3] = new AGNRotationalParameterHandle( this, points[3], rotHandleAxisPolar,
                                                                        recentPolarValue, ID_ROT_POLAR );
        handles[4] = new AGNRotationalParameterHandle( this, points[4], rotHandleAxisAzimuthal, 
                                                                        recentAzimuthalValue, ID_ROT_AZIMUTHAL );
    }
}
